import { Prisma, PrismaClient } from '@prisma/client'
import { priceService } from './priceService'
import { DAILY_DISTRIBUTION_COUNT, DISTRIBUTION_INTERVAL_HOURS } from '../constants/reward'
import { rewardDistributionStatusService } from './rewardDistributionStatusService'
import { addBeijingHours, formatBeijingTime, getBeijingStartOfDay, isSameBeijingDay } from '../utils/timezone'
import { calculateManualAdjustmentState, ManualAdjustmentResult } from '../utils/stakingAdjustment'

const prisma = new PrismaClient()

export class RewardCalculationService {
  /**
   * 【每天凌晨0点执行】计算所有用户的当日收益
   */
  async calculateDailyRewards(): Promise<void> {
    try {
      console.log('=== 开始计算所有用户的当日收益 ===')

      // 获取所有有质押的用户
      const allStakings = await prisma.userStaking.findMany({
        where: {
          OR: [
            { stakedAmount: { gt: 0 } },
            { lockedAmount: { gt: 0 } }
          ]
        }
      })

      if (allStakings.length === 0) {
        console.log('没有活跃的质押记录')
        return
      }

      const now = new Date()
      const distributionStatus = await rewardDistributionStatusService.getOrCreateStatusForDate(now)
      const firstDistributionTime = distributionStatus.nextDistributionAt ?? now

      // 批量计算每个用户的当日收益
      for (const staking of allStakings) {
        const stakedAmount = Number(staking.stakedAmount)
        const totalBase = stakedAmount
        const dailyRate = Number(staking.dailyRewardRate)

        // 计算当日总收益
        const dailyReward = totalBase * dailyRate

        if (dailyReward > 0) {
          const isSameDay = isSameBeijingDay(staking.lastCalculationDate, now)
          const preservedDistributionCount = isSameDay ? staking.dailyDistributionCount : 0
          const preservedDistributedAmount = isSameDay ? Number(staking.dailyRewardDistributed) : 0
          const preservedNextDistribution = isSameDay ? staking.nextDistributionAt ?? firstDistributionTime : firstDistributionTime

          await prisma.userStaking.update({
            where: { userId: staking.userId },
            data: {
              currentDailyReward: dailyReward,
              dailyRewardDistributed: preservedDistributedAmount,
              dailyDistributionCount: preservedDistributionCount,
              nextDistributionAt: preservedDistributionCount >= DAILY_DISTRIBUTION_COUNT ? null : preservedNextDistribution,
              lastCalculationDate: now
            }
          })

          const nextDistributionLabel = preservedDistributionCount >= DAILY_DISTRIBUTION_COUNT || !preservedNextDistribution
            ? '已完成'
            : formatBeijingTime(preservedNextDistribution)

          console.log(
            `✅ 用户 ${staking.userId}: 本金 ${stakedAmount.toFixed(4)} NEAR, 日收益率 ${(dailyRate * 100).toFixed(2)}%, ` +
            `当日总收益 ${dailyReward.toFixed(4)} NEAR, 已发次数 ${preservedDistributionCount}/${DAILY_DISTRIBUTION_COUNT}, ` +
            `下次发放: ${nextDistributionLabel}`
          )
        }
      }

      console.log(`=== 完成 ${allStakings.length} 位用户的当日收益计算 ===`)

    } catch (error) {
      console.error('计算当日收益失败:', error)
      throw error
    }
  }

  /**
   * 【每2小时执行一次】发放收益
   */
  async distributeRewards(executionTime: Date = new Date(), targetSlotIndex?: number): Promise<number> {
    try {
      const slotIndex = targetSlotIndex ?? await this.resolveTargetSlotIndex(executionTime)
      const executionLabel = formatBeijingTime(executionTime)
      console.log(`=== 开始分次发放收益 [${executionLabel}] (当前槽位 #${slotIndex + 1}) ===`)

      const stakingsToDistribute = await prisma.userStaking.findMany({
        where: {
          AND: [
            { currentDailyReward: { gt: 0 } },
            { dailyDistributionCount: { lt: DAILY_DISTRIBUTION_COUNT } },
            {
              OR: [
                { nextDistributionAt: { lte: executionTime } },
                { nextDistributionAt: null }
              ]
            }
          ]
        }
      })

      if (stakingsToDistribute.length === 0) {
        console.log(`⏸️  当前没有需要发放的收益 [${executionLabel}]`)
        return 0
      }

      console.log(`📋 找到 ${stakingsToDistribute.length} 位用户需要发放收益`)

      let currentPrice: number
      try {
        currentPrice = await priceService.getNearPrice()
        console.log(`✅ NEAR 价格获取成功: ₩${currentPrice.toFixed(2)}`)
      } catch (error) {
        console.error('❌ 获取NEAR价格失败,收益发放将暂停:', error)
        throw new Error(
          `获取 NEAR 价格失败，收益发放终止：${error instanceof Error ? error.message : String(error)}`
        )
      }

      let processedUsers = 0
      for (const staking of stakingsToDistribute) {
        const distributed = await this.distributeRewardForUser(staking, currentPrice, executionTime, slotIndex)
        if (distributed) {
          processedUsers += 1
        }
      }

      console.log(`=== 完成 ${processedUsers} 位用户的收益发放 ===`)

      return processedUsers

    } catch (error) {
      console.error('分次发放收益失败:', error)
      throw error
    }
  }

  private async resolveTargetSlotIndex(referenceTime: Date): Promise<number> {
    const status = await rewardDistributionStatusService.getOrCreateStatusForDate(referenceTime)
    return status.distributionCount
  }

  /**
   * 一次性补发额外收益：为所有用户追加若干天的奖励，仍按每日12次拆分
   */
  async grantBonusRewards(dayCount: number): Promise<{
    processedUsers: number
    totalInstallments: number
    totalRewardNear: number
    totalRewardKrw: number
  }> {
    if (dayCount <= 0) {
      throw new Error('dayCount 必须大于 0')
    }

    console.log(`=== 开始执行一次性补发收益：共 ${dayCount} 天，预计 ${DAILY_DISTRIBUTION_COUNT * dayCount} 次发放 ===`)

    const stakings = await prisma.userStaking.findMany({
      where: {
        stakedAmount: { gt: 0 }
      }
    })

    if (stakings.length === 0) {
      console.log('没有找到需要补发收益的质押用户。')
      return {
        processedUsers: 0,
        totalInstallments: 0,
        totalRewardNear: 0,
        totalRewardKrw: 0
      }
    }

    let currentPrice: number
    try {
      currentPrice = await priceService.getNearPrice()
      console.log(`✅ 当前 NEAR 价格: ₩${currentPrice.toFixed(2)}`)
    } catch (error) {
      console.error('❌ 获取 NEAR 价格失败，终止补发流程:', error)
      throw error
    }

    let processedUsers = 0
    let totalInstallments = 0
    let totalRewardNear = 0

    for (const staking of stakings) {
      const stakedAmount = Number(staking.stakedAmount)
      const dailyRate = Number(staking.dailyRewardRate)

      if (stakedAmount <= 0 || dailyRate <= 0) {
        continue
      }

      const dailyReward = stakedAmount * dailyRate
      if (dailyReward <= 0) {
        continue
      }

      const bonusInfo = await this.grantBonusRewardsForUser(
        staking.userId,
        dailyReward,
        dayCount,
        currentPrice
      )

      if (bonusInfo.installments > 0) {
        processedUsers += 1
        totalInstallments += bonusInfo.installments
        totalRewardNear += bonusInfo.totalRewardNear
        console.log(
          `🎁 用户 ${staking.userId}: 额外发放 ${bonusInfo.totalRewardNear.toFixed(4)} NEAR ` +
          `(${(bonusInfo.totalRewardNear * currentPrice).toFixed(0)} KRW)，` +
          `共 ${bonusInfo.installments} 次`
        )
      }
    }

    const totalRewardKrw = totalRewardNear * currentPrice

    console.log('=== 一次性补发收益完成 ===')
    console.log(`👥 用户数量: ${processedUsers}`)
    console.log(`📦 发放次数: ${totalInstallments}`)
    console.log(`💰 合计 NEAR: ${totalRewardNear.toFixed(4)}`)
    console.log(`₩ 合计 KRW: ${totalRewardKrw.toFixed(0)}`)

    return {
      processedUsers,
      totalInstallments,
      totalRewardNear,
      totalRewardKrw
    }
  }

  private async grantBonusRewardsForUser(
    userId: number,
    dailyReward: number,
    dayCount: number,
    currentPrice: number
  ): Promise<{ installments: number; totalRewardNear: number }> {
    const installmentsPerDay = DAILY_DISTRIBUTION_COUNT
    let totalRewardNear = 0
    let actualInstallments = 0
    const now = new Date()

    for (let dayIndex = 0; dayIndex < dayCount; dayIndex++) {
      let remainingAmount = dailyReward

      for (let slotIndex = 0; slotIndex < installmentsPerDay; slotIndex++) {
        const remainingSlots = installmentsPerDay - slotIndex
        let amountNear: number

        if (slotIndex === installmentsPerDay - 1) {
          amountNear = Number(remainingAmount.toFixed(4))
        } else {
          amountNear = Math.floor((remainingAmount / remainingSlots) * 10000) / 10000
          remainingAmount = Number((remainingAmount - amountNear).toFixed(4))
        }

        if (amountNear <= 0) {
          continue
        }

        const bonusTimestamp = new Date(
          now.getTime() + dayIndex * 24 * 60 * 60 * 1000 + slotIndex * DISTRIBUTION_INTERVAL_HOURS * 60 * 60 * 1000
        )

        await this.applyBonusReward(userId, amountNear, currentPrice, bonusTimestamp, {
          dayIndex,
          slotIndex
        })

        totalRewardNear += amountNear
        actualInstallments += 1
      }
    }

    return {
      installments: actualInstallments,
      totalRewardNear
    }
  }

  private async applyBonusReward(
    userId: number,
    amountNear: number,
    currentPrice: number,
    timestamp: Date,
    context: { dayIndex: number; slotIndex: number }
  ): Promise<void> {
    const rewardKrw = amountNear * currentPrice
    const distributionKey = this.buildBonusDistributionKey(userId, timestamp, context.dayIndex, context.slotIndex)
    const txHash = this.buildDistributionTxHash(distributionKey)

    try {
      await prisma.$transaction(async (tx) => {
        await tx.userStaking.update({
          where: { userId },
          data: {
            totalRewardsNear: { increment: amountNear },
            availableRewards: { increment: amountNear },
            lastSettlementDate: timestamp
          }
        })

        await tx.userBalance.upsert({
          where: { userId },
          update: {
            balance: { increment: rewardKrw },
            total: { increment: rewardKrw }
          },
          create: {
            userId,
            balance: rewardKrw,
            frozen: 0,
            total: rewardKrw
          }
        })

        await tx.stakingReward.create({
          data: {
            userId,
            amount: amountNear,
            txHash,
            createdAt: timestamp,
            distributionKey
          }
        })
      })
    } catch (error) {
      console.error(
        `用户 ${userId} 在补发收益 d${context.dayIndex + 1}/s${context.slotIndex + 1} 时失败:`,
        error
      )
      throw error
    }

    await this.distributeReferralReward(userId, amountNear, currentPrice, timestamp)
  }

  /**
   * 为单个用户发放收益
   */
  private async distributeRewardForUser(
    staking: any,
    currentPrice: number,
    executionTime: Date,
    slotIndex: number
  ): Promise<boolean> {
    const userId = staking.userId
    const currentDailyReward = Number(staking.currentDailyReward)
    const dailyRewardDistributed = Number(staking.dailyRewardDistributed)
    const dailyDistributionCount = Number(staking.dailyDistributionCount)

    if (dailyDistributionCount >= DAILY_DISTRIBUTION_COUNT) {
      console.log(`ℹ️ 用户 ${userId} 已完成 ${DAILY_DISTRIBUTION_COUNT} 次发放，跳过本轮。`)
      return false
    }

    if (dailyDistributionCount >= slotIndex + 1) {
      console.log(`ℹ️ 用户 ${userId} 在槽位 #${slotIndex + 1} 前已发放 ${dailyDistributionCount} 次，跳过重复发放。`)
      return false
    }

    // 计算本次发放金额
    let amountToDistribute: number
    const remainingCount = DAILY_DISTRIBUTION_COUNT - dailyDistributionCount
    const remainingAmount = currentDailyReward - dailyRewardDistributed

    if (dailyDistributionCount === DAILY_DISTRIBUTION_COUNT - 1) {
      // 最后一次发放：发放剩余全部金额，避免精度损失
      amountToDistribute = remainingAmount
    } else {
      // ✅ 修复：根据剩余金额和剩余次数计算，而不是总金额/12
      // 这样可以正确处理等级升级后的情况
      amountToDistribute = Math.floor((remainingAmount / remainingCount) * 100) / 100
    }

    const distributionSlot = dailyDistributionCount
    const distributionKey = this.buildDailyDistributionKey(userId, executionTime, distributionSlot)
    const txHash = this.buildDistributionTxHash(distributionKey)

    // 更新用户质押信息
    const newDistributionCount = dailyDistributionCount + 1
    const newDistributedAmount = dailyRewardDistributed + amountToDistribute
    const totalRewardsNear = Number(staking.totalRewardsNear) + amountToDistribute

    // 计算下次发放时间
    let nextDistributionAt: Date | null = null
    if (newDistributionCount < DAILY_DISTRIBUTION_COUNT) {
      nextDistributionAt = addBeijingHours(executionTime, DISTRIBUTION_INTERVAL_HOURS)
    }

    // 计算KRW金额
    const rewardKrw = amountToDistribute * currentPrice

    try {
      await prisma.$transaction(async (tx) => {
        // 1. 更新用户质押信息
        await tx.userStaking.update({
          where: { userId },
          data: {
            totalRewardsNear,
            availableRewards: {
              increment: amountToDistribute
            },
            dailyRewardDistributed: newDistributedAmount,
            dailyDistributionCount: newDistributionCount,
            nextDistributionAt,
            lastSettlementDate: executionTime
          }
        })

        // 2. 更新用户余额（收益KRW转入可用余额）
        await tx.userBalance.upsert({
          where: { userId },
          update: {
            balance: { increment: rewardKrw },
            total: { increment: rewardKrw }
          },
          create: {
            userId,
            balance: rewardKrw,
            frozen: 0,
            total: rewardKrw
          }
        })

        // 3. 记录奖励
        await tx.stakingReward.create({
          data: {
            userId,
            amount: amountToDistribute,
            txHash,
            distributionKey
          }
        })

        // 4. 记录结算明细，便于钱包端按结算口径统计
        await tx.stakingSettlement.create({
          data: {
            userId,
            stakedAmount: Number(staking.stakedAmount || 0),
            dailyRewardRate: Number(staking.dailyRewardRate || 0),
            rewardNear: amountToDistribute,
            rewardKrw: rewardKrw,
            nearPrice: currentPrice,
            settlementDate: executionTime,
            distributionKey
          }
        })
      }, { timeout: 30000 })
    } catch (error) {
      console.error(`用户 ${staking.userId} 收益发放失败:`, error)
      throw error
    }

    console.log(
      `💰 用户 ${userId}: 第 ${newDistributionCount}/${DAILY_DISTRIBUTION_COUNT} 次发放, ` +
      `金额 ${amountToDistribute.toFixed(4)} NEAR (₩${rewardKrw.toFixed(0)}), ` +
      `累计收益 ${totalRewardsNear.toFixed(4)} NEAR, ` +
      `下次发放: ${nextDistributionAt ? formatBeijingTime(nextDistributionAt) : '已完成'}`
    )

    // 【新增】处理推荐奖励：如果该用户有推荐人，给推荐人发放10%的收益奖励
    await this.distributeReferralReward(userId, amountToDistribute, currentPrice, executionTime)

    return true
  }

  /**
   * 【新增】发放质押收益的推荐奖励
   * 当B用户获得质押收益时，给其推荐人A发放10%的推荐奖励
   */
  private async distributeReferralReward(
    userId: number,
    userRewardAmount: number,
    currentPrice: number,
    now: Date
  ): Promise<void> {
    try {
      // 查询用户的推荐人
      const user = await prisma.user.findUnique({
        where: { id: userId },
        select: {
          referredBy: true,
          name: true
        }
      })

      // 如果没有推荐人，直接返回
      if (!user || !user.referredBy) {
        return
      }

      const referrerId = user.referredBy
      const referralRewardNEAR = userRewardAmount * 0.10 // 10%推荐奖励
      const referralRewardKRW = referralRewardNEAR * currentPrice

      // 事务处理：更新推荐人的lockedAmount并记录奖励
      await prisma.$transaction(async (tx) => {
        // 检查推荐人是否有UserStaking记录
        const referrerStaking = await tx.userStaking.findUnique({
          where: { userId: referrerId }
        })

        if (referrerStaking) {
          // 如果有，累加到lockedAmount
          await tx.userStaking.update({
            where: { userId: referrerId },
            data: {
              lockedAmount: { increment: referralRewardNEAR }
            }
          })
        } else {
          // 如果没有，创建UserStaking记录
          // 获取推荐人当前等级的日收益率
          const { getUserDailyRewardRate } = await import('../utils/tierHelper')
          const referrerDailyRate = await getUserDailyRewardRate(referrerId)

          await tx.userStaking.create({
            data: {
              userId: referrerId,
              stakedAmount: 0,
              lockedAmount: referralRewardNEAR,
              totalRewardsNear: 0,
              dailyRewardRate: referrerDailyRate
            }
          })
        }

        // 创建推荐奖励记录
        await tx.stakingReferralReward.create({
          data: {
            userId: referrerId,
            amount: referralRewardKRW,
            amountNear: referralRewardNEAR,
            source: `추천 사용자 ${user.name}(ID:${userId})의 스테이킹 수익 ${userRewardAmount.toFixed(4)} NEAR의 10%`
          }
        })
      })

      console.log(
        `✅ 스테이킹 추천 보상 지급: 추천인 ${referrerId}이(가) ` +
        `${referralRewardNEAR.toFixed(4)} NEAR (${referralRewardKRW.toLocaleString('ko-KR')} KRW) 획득 ` +
        `(피추천인 ${userId}의 수익 ${userRewardAmount.toFixed(4)} NEAR의 10%)`
      )

    } catch (error) {
      console.error(`❌ 사용자 ${userId}의 추천 보상 처리 실패:`, error)
      // 不阻断收益发放流程，仅记录错误
    }
  }

  /**
   * 【废弃】旧的收益更新方法
   */
  async updateAllStakingRewards(): Promise<void> {
    console.warn('updateAllStakingRewards 已废弃，请使用 calculateDailyRewards 和 distributeRewards')
    await this.calculateDailyRewards()
  }

  /**
   * 【废弃】旧的单用户收益计算方法
   */
  private async calculateAndUpdateStakingRewards(staking: any, currentPrice: number): Promise<void> {
    console.warn('calculateAndUpdateStakingRewards 已废弃')
  }

  /**
   * 【追加投资时调用】重新计算当日收益
   * 当用户充值增加本金或等级升级时，重新计算当日总收益
   * ✅ 修复：保留已发放进度，避免重复发放
   */
  async recalculateDailyReward(userId: number): Promise<void> {
    try {
      console.log(`=== 用户 ${userId} 追加投资或等级变更，重新计算当日收益 ===`)

      const staking = await prisma.userStaking.findUnique({
        where: { userId }
      })

      if (!staking) {
        console.log(`用户 ${userId} 没有质押记录`)
        return
      }

      const now = new Date()
      const lastCalculationDate = staking.lastCalculationDate

      // 检查是否在同一天
      const isSameDay = lastCalculationDate &&
        lastCalculationDate.getFullYear() === now.getFullYear() &&
        lastCalculationDate.getMonth() === now.getMonth() &&
        lastCalculationDate.getDate() === now.getDate()

      // 如果不是同一天，或者还没有计算过，不重新计算
      if (!isSameDay) {
        console.log(`用户 ${userId} 不在同一天追加投资，不重新计算`)
        return
      }

      // 重新计算当日总收益（仅依据实际质押本金）
      const stakedAmount = Number(staking.stakedAmount)
      const totalBase = stakedAmount
      const dailyRate = Number(staking.dailyRewardRate)
      const oldDailyReward = Number(staking.currentDailyReward)
      const newDailyReward = totalBase * dailyRate

      // ✅ 修复：保留已发放的进度
      const alreadyDistributed = Number(staking.dailyRewardDistributed)
      const alreadyCount = Number(staking.dailyDistributionCount)
      const remainingCount = DAILY_DISTRIBUTION_COUNT - alreadyCount

      console.log(`📊 收益重新计算详情:`)
      console.log(`   - 本金: ${stakedAmount.toFixed(4)} NEAR`)
      console.log(`   - 旧收益率: ${(Number(staking.dailyRewardRate) * 100).toFixed(2)}%`)
      console.log(`   - 新收益率: ${(dailyRate * 100).toFixed(2)}%`)
      console.log(`   - 旧当日总收益: ${oldDailyReward.toFixed(4)} NEAR`)
      console.log(`   - 新当日总收益: ${newDailyReward.toFixed(4)} NEAR`)
      console.log(`   - 已发放: ${alreadyDistributed.toFixed(4)} NEAR (${alreadyCount}/${DAILY_DISTRIBUTION_COUNT}次)`)
      console.log(`   - 剩余次数: ${remainingCount}次`)

      // 如果还没开始发放，按正常流程设置首次发放时间
      let nextDistributionAt: Date | null
      if (alreadyCount === 0) {
        // 还没发放过，设置为立即可发放
        nextDistributionAt = new Date(now.getTime() + 1000)
        console.log(`   - 首次发放时间: ${formatBeijingTime(nextDistributionAt)}`)
      } else if (remainingCount > 0) {
        // 已经发放过一些，保持原有的下次发放时间
        nextDistributionAt = staking.nextDistributionAt
        console.log(`   - 保持原下次发放时间: ${nextDistributionAt ? formatBeijingTime(nextDistributionAt) : '未设置'}`)
      } else {
        // 已经发完了，不再发放
        nextDistributionAt = null
        console.log(`   - 今日发放已完成`)
      }

      // ✅ 只更新当日总收益，不重置发放进度
      await prisma.userStaking.update({
        where: { userId },
        data: {
          currentDailyReward: newDailyReward,
          // ✅ 不重置 dailyRewardDistributed
          // ✅ 不重置 dailyDistributionCount
          nextDistributionAt,
          lastCalculationDate: now
        }
      })

      console.log(
        `✅ 用户 ${userId} 收益重新计算完成: ` +
        `新本金 ${totalBase.toFixed(4)} NEAR, ` +
        `新当日总收益 ${newDailyReward.toFixed(4)} NEAR, ` +
        `剩余${remainingCount}次发放`
      )

    } catch (error) {
      console.error(`用户 ${userId} 重新计算收益失败:`, error)
      throw error
    }
  }

  /**
   * 관리자 수동 조정 후 당일 분배 현황을 안전하게 동기화
   */
  async syncAfterManualAdjustment(userId: number): Promise<ManualAdjustmentResult> {
    const staking = await prisma.userStaking.findUnique({
      where: { userId }
    })

    if (!staking) {
      throw new Error(`사용자 ${userId}의 스테이킹 기록을 찾을 수 없습니다`)
    }

    const now = new Date()
    const stakedAmount = Number(staking.stakedAmount)

    const adjustmentState = calculateManualAdjustmentState({
      stakedAmount,
      dailyRewardRate: Number(staking.dailyRewardRate),
      dailyRewardDistributed: Number(staking.dailyRewardDistributed),
      dailyDistributionCount: Number(staking.dailyDistributionCount),
      nextDistributionAt: staking.nextDistributionAt,
      now
    })

    await prisma.userStaking.update({
      where: { userId },
      data: {
        currentDailyReward: adjustmentState.effectiveDailyReward,
        dailyRewardDistributed: adjustmentState.distributedToday,
        dailyDistributionCount: adjustmentState.dailyDistributionCount,
        nextDistributionAt: adjustmentState.nextDistributionAt,
        lastCalculationDate: now
      }
    })

    console.log(
      `🔄 사용자 ${userId} 수동 조정 동기화:` +
      ` 본금 ${stakedAmount.toFixed(4)} NEAR,` +
      ` 목표 일일 수익 ${adjustmentState.targetDailyReward.toFixed(4)} NEAR,` +
      ` 기준 일일 수익 ${adjustmentState.effectiveDailyReward.toFixed(4)} NEAR,` +
      ` 이미 발송 ${adjustmentState.distributedToday.toFixed(4)} NEAR,` +
      ` 잔여 ${adjustmentState.remainingToday.toFixed(4)} NEAR`
    )

    if (adjustmentState.cappedDueToReduction) {
      console.log(`⚠️ 사용자 ${userId}: 이미 발송된 금액이 조정된 본금을 초과하여 금일 추가 발송이 중단됩니다.`)
    } else if (adjustmentState.scheduleClosedToday) {
      console.log(`ℹ️ 사용자 ${userId}: 금일 분배는 이미 완료되었으며 변경 사항은 익일 계산에 반영됩니다.`)
    } else if (adjustmentState.nextDistributionAt) {
      console.log(`⏭️ 사용자 ${userId}: 다음 분배 예정 시각 ${formatBeijingTime(adjustmentState.nextDistributionAt)}`)
    }

    return adjustmentState
  }

  /**
   * 批准质押申请 - 已废弃
   */
  async approveStakingOrder(orderId: number): Promise<void> {
    console.warn('approveStakingOrder는 폐기되었습니다: 새 시스템에서는 충전 금额이 바로 UserStaking으로 들어갑니다.')
  }

  /**
   * 计算单个用户的总收益统计
   */
  async getUserRewardStats(userId: number): Promise<{
    totalInvestment: number
    totalRewards: number
    availableRewards: number
    activeOrders: number
  }> {
    try {
      // 查询用户的UserStaking记录
      const userStaking = await prisma.userStaking.findUnique({
        where: { userId }
      })

      if (!userStaking) {
        return {
          totalInvestment: 0,
          totalRewards: 0,
          availableRewards: 0,
          activeOrders: 0
        }
      }

      const stakedAmount = Number(userStaking.stakedAmount)
      const totalRewardsNear = Number(userStaking.totalRewardsNear)
      let availableRewardsNear = Number(userStaking.availableRewards || 0)

      // 获取NEAR价格转换为KRW
      const nearPrice = await priceService.getNearPrice()

      if (availableRewardsNear <= 0) {
        availableRewardsNear = totalRewardsNear
      }

      return {
        totalInvestment: stakedAmount * nearPrice,
        totalRewards: totalRewardsNear * nearPrice,
        availableRewards: availableRewardsNear * nearPrice,
        activeOrders: stakedAmount > 0 ? 1 : 0
      }

    } catch (error) {
      console.error(`사용자 ${userId} 수익 통계 조회 실패:`, error)
      throw error
    }
  }

  /**
   * 手动触发收益计算（用于测试）
   */
  async manualCalculateRewards(): Promise<string> {
    try {
      await this.updateAllStakingRewards()
      return '수익 계산이 완료되었습니다.'
    } catch (error) {
      console.error('수익 계산 수동 실행 실패:', error)
      const errorMessage = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.'
      return `수익 계산에 실패했습니다.: ${errorMessage}`
    }
  }

  private getStartOfDayIso(date: Date): string {
    return getBeijingStartOfDay(date).toISOString()
  }

  private getStartOfDay(date: Date): Date {
    return getBeijingStartOfDay(date)
  }

  private extractSlotIndex(distributionKey: string): number | null {
    const parts = distributionKey.split(':')
    if (parts.length < 4) {
      return null
    }
    const slotPart = parts[parts.length - 1]
    const slot = Number(slotPart)
    return Number.isFinite(slot) ? slot : null
  }

  private async getNearPriceForDay(userId: number, dayStart: Date): Promise<number> {
    const nextDay = addBeijingHours(dayStart, 24)

    const settlement = await prisma.stakingSettlement.findFirst({
      where: {
        userId,
        settlementDate: {
          gte: dayStart,
          lt: nextDay
        }
      },
      orderBy: {
        settlementDate: 'desc'
      },
      select: {
        nearPrice: true
      }
    })

    if (settlement?.nearPrice) {
      return Number(settlement.nearPrice)
    }

    return priceService.getNearPrice()
  }

  private async applyHistoricalDailyReward(
    staking: {
      userId: number
      stakedAmount: Prisma.Decimal | number
      dailyRewardRate: Prisma.Decimal | number
      createdAt?: Date | null
    },
    amountNear: number,
    nearPrice: number,
    timestamp: Date,
    slotIndex: number
  ): Promise<boolean> {
    const userId = staking.userId
    const distributionKey = this.buildDailyDistributionKey(userId, timestamp, slotIndex)
    const txHash = this.buildDistributionTxHash(distributionKey)
    const rewardKrw = amountNear * nearPrice

    try {
      await prisma.$transaction([
        prisma.userStaking.update({
          where: { userId },
          data: {
            totalRewardsNear: { increment: amountNear },
            availableRewards: { increment: amountNear },
            lastSettlementDate: timestamp
          }
        }),
        prisma.userBalance.upsert({
          where: { userId },
          update: {
            balance: { increment: rewardKrw },
            total: { increment: rewardKrw }
          },
          create: {
            userId,
            balance: rewardKrw,
            frozen: 0,
            total: rewardKrw
          }
        }),
        prisma.stakingReward.create({
          data: {
            userId,
            amount: amountNear,
            txHash,
            createdAt: timestamp,
            distributionKey
          }
        }),
        prisma.stakingSettlement.create({
          data: {
            userId,
            stakedAmount: Number(staking.stakedAmount || 0),
            dailyRewardRate: Number(staking.dailyRewardRate || 0),
            rewardNear: amountNear,
            rewardKrw,
            nearPrice,
            settlementDate: timestamp,
            distributionKey
          }
        })
      ])
    } catch (error) {
      console.error(`用户 ${userId} 的历史补发槽位 ${slotIndex} 处理失败:`, error)
      throw error
    }

    await this.distributeReferralReward(userId, amountNear, nearPrice, timestamp)
    return true
  }

  /**
   * 手动补发指定用户当日缺失的收益槽位
   * @param userId 用户ID
   * @param options.targetSlotCount 目标发放次数（默认与当日全局计数保持一致）
   * @param options.date 参考日期（默认今日）
   */
  async catchUpUserDistributions(
    userId: number,
    options?: { targetSlotCount?: number; date?: Date; dayStartOverride?: Date }
  ): Promise<{
    processedInstallments: number
    targetSlotCount: number
    finalDistributionCount: number
  }> {
    const referenceDate = options?.date ?? new Date()

    let status: Awaited<ReturnType<typeof rewardDistributionStatusService.getOrCreateStatusForDate>> | null = null
    if (options?.targetSlotCount === undefined) {
      status = await rewardDistributionStatusService.getOrCreateStatusForDate(referenceDate)
    }

    const targetSlotCount = Math.min(
      options?.targetSlotCount ?? status?.distributionCount ?? 0,
      DAILY_DISTRIBUTION_COUNT
    )

    let staking = await prisma.userStaking.findUnique({
      where: { userId }
    })

    if (!staking) {
      console.warn(`⚠️ 用户 ${userId} 没有质押记录，跳过补发`)
      return {
        processedInstallments: 0,
        targetSlotCount,
        finalDistributionCount: 0
      }
    }

    if (Number(staking.currentDailyReward) <= 0) {
      console.warn(`⚠️ 用户 ${userId} 当前无日收益，跳过补发`)
      return {
        processedInstallments: 0,
        targetSlotCount,
        finalDistributionCount: staking.dailyDistributionCount
      }
    }

    if (staking.dailyDistributionCount >= targetSlotCount) {
      console.log(
        `ℹ️ 用户 ${userId} 的发放次数 (${staking.dailyDistributionCount}) 已达到目标 ${targetSlotCount}，无需补发`
      )
      return {
        processedInstallments: 0,
        targetSlotCount,
        finalDistributionCount: staking.dailyDistributionCount
      }
    }

    const nearPrice = await priceService.getNearPrice()
    const dayStart =
      options?.dayStartOverride ??
      (status ? status.date : rewardDistributionStatusService.getStartOfDay(referenceDate))
    let processedInstallments = 0

    while (
      staking &&
      staking.dailyDistributionCount < targetSlotCount &&
      Number(staking.currentDailyReward) > 0
    ) {
      const slotIndex = staking.dailyDistributionCount
      const executionTime = new Date(
        dayStart.getTime() + slotIndex * DISTRIBUTION_INTERVAL_HOURS * 60 * 60 * 1000
      )

      await this.distributeRewardForUser(staking, nearPrice, executionTime, slotIndex)
      processedInstallments += 1

      staking = await prisma.userStaking.findUnique({
        where: { userId }
      })
    }

    return {
      processedInstallments,
      targetSlotCount,
      finalDistributionCount: staking?.dailyDistributionCount ?? 0
    }
  }

  /**
   * 逐日补发：从 startDate 到 endDate（含）检查每天是否少发，并补齐缺失槽位
   */
  async backfillUserRewards(
    userId: number,
    options?: { startDate?: Date; endDate?: Date }
  ): Promise<{
    processedDays: number
    processedInstallments: number
    totalRewardNear: number
  }> {
    const staking = await prisma.userStaking.findUnique({
      where: { userId }
    })

    if (!staking) {
      console.warn(`⚠️ 用户 ${userId} 无质押记录，跳过补发`)
      return {
        processedDays: 0,
        processedInstallments: 0,
        totalRewardNear: 0
      }
    }

    if (Number(staking.stakedAmount) <= 0 || Number(staking.dailyRewardRate) <= 0) {
      console.warn(`⚠️ 用户 ${userId} 无有效收益率，跳过补发`)
      return {
        processedDays: 0,
        processedInstallments: 0,
        totalRewardNear: 0
      }
    }

    const stakingCreatedAt = staking.createdAt ?? new Date()
    const startDate = options?.startDate ?? stakingCreatedAt
    const endDate = options?.endDate ?? new Date()

    const dayCursor = this.getStartOfDay(new Date(startDate))
    const endDay = this.getStartOfDay(new Date(endDate))

    if (dayCursor > endDay) {
      return {
        processedDays: 0,
        processedInstallments: 0,
        totalRewardNear: 0
      }
    }

    let processedDays = 0
    let processedInstallments = 0
    let totalRewardNear = 0

    while (dayCursor <= endDay) {
      if (dayCursor >= this.getStartOfDay(stakingCreatedAt)) {
        const result = await this.backfillUserRewardsForDay(userId, staking, new Date(dayCursor), DAILY_DISTRIBUTION_COUNT)
        if (result.installments > 0) {
          processedDays += 1
          processedInstallments += result.installments
          totalRewardNear += result.totalRewardNear
        }
      }
      dayCursor.setUTCDate(dayCursor.getUTCDate() + 1)
    }

    return {
      processedDays,
      processedInstallments,
      totalRewardNear
    }
  }

  private async backfillUserRewardsForDay(
    userId: number,
    staking: {
      userId: number
      stakedAmount: Prisma.Decimal | number
      dailyRewardRate: Prisma.Decimal | number
      createdAt?: Date | null
    },
    dayStart: Date,
    targetSlotCount: number
  ): Promise<{ installments: number; totalRewardNear: number }> {
    if (targetSlotCount <= 0) {
      return { installments: 0, totalRewardNear: 0 }
    }

    const dayIso = this.getStartOfDayIso(dayStart)
    const prefix = `daily:${userId}:${dayIso}`
    const existingRewards = await prisma.stakingReward.findMany({
      where: {
        userId,
        distributionKey: {
          startsWith: `${prefix}:`
        }
      },
      orderBy: {
        distributionKey: 'asc'
      }
    })

    const existingSlots = new Set<number>()
    let existingAmount = 0
    for (const reward of existingRewards) {
      if (reward.distributionKey) {
        const slot = this.extractSlotIndex(reward.distributionKey)
        if (slot !== null) {
          existingSlots.add(slot)
        }
      }
      existingAmount += Number(reward.amount)
    }

    if (existingSlots.size >= targetSlotCount) {
      return { installments: 0, totalRewardNear: 0 }
    }

    const perSlotReward = Number(
      ((Number(staking.stakedAmount) * Number(staking.dailyRewardRate)) / DAILY_DISTRIBUTION_COUNT).toFixed(4)
    )
    const activationTime = staking.createdAt ?? dayStart

    const eligibleSlots: number[] = []
    const ineligibleSlots: number[] = []

    for (let slotIndex = 0; slotIndex < targetSlotCount; slotIndex++) {
      const slotTime = new Date(
        dayStart.getTime() + slotIndex * DISTRIBUTION_INTERVAL_HOURS * 60 * 60 * 1000
      )
      if (slotTime < activationTime) {
        ineligibleSlots.push(slotIndex)
      } else {
        eligibleSlots.push(slotIndex)
      }
    }

    if (eligibleSlots.length === 0) {
      return { installments: 0, totalRewardNear: 0 }
    }

    const expectedTotal = Number((perSlotReward * eligibleSlots.length).toFixed(4))
    let remainingAmount = Number((expectedTotal - existingAmount).toFixed(4))

    if (remainingAmount <= 0) {
      return { installments: 0, totalRewardNear: 0 }
    }

    const nearPrice = await this.getNearPriceForDay(userId, dayStart)

    const missingSlots: number[] = []
    for (const slotIndex of eligibleSlots) {
      if (!existingSlots.has(slotIndex)) {
        missingSlots.push(slotIndex)
      }
    }

    if (missingSlots.length === 0) {
      return { installments: 0, totalRewardNear: 0 }
    }

    let processedInstallments = 0
    let totalRewardIssued = 0

    for (let i = 0; i < missingSlots.length; i++) {
      const slotIndex = missingSlots[i]
      const remainingSlots = missingSlots.length - i
      let amountNear: number

      if (i === missingSlots.length - 1) {
        amountNear = Number(remainingAmount.toFixed(4))
      } else {
        amountNear = Math.min(perSlotReward, Number((remainingAmount / remainingSlots).toFixed(4)))
      }

      if (amountNear <= 0) {
        continue
      }

      const slotTime = new Date(
        dayStart.getTime() + slotIndex * DISTRIBUTION_INTERVAL_HOURS * 60 * 60 * 1000
      )

      const applied = await this.applyHistoricalDailyReward(staking, amountNear, nearPrice, slotTime, slotIndex)

      if (applied) {
        processedInstallments += 1
        totalRewardIssued += amountNear
        remainingAmount = Number((remainingAmount - amountNear).toFixed(4))
      }
    }

    const isCurrentDay = dayStart.getTime() === this.getStartOfDay(new Date()).getTime()
    if (isCurrentDay && processedInstallments > 0) {
      const updatedCount = Math.min(targetSlotCount, existingSlots.size + processedInstallments)
      const updatedDistributed = Number((existingAmount + totalRewardIssued).toFixed(4))
      let nextDistributionAt: Date | null = null
      if (updatedCount < DAILY_DISTRIBUTION_COUNT) {
        nextDistributionAt = new Date(
          dayStart.getTime() + updatedCount * DISTRIBUTION_INTERVAL_HOURS * 60 * 60 * 1000
        )
      }

      await prisma.userStaking.update({
        where: { userId },
        data: {
          dailyDistributionCount: updatedCount,
          dailyRewardDistributed: updatedDistributed,
          nextDistributionAt
        }
      })
    }

    return {
      installments: processedInstallments,
      totalRewardNear: totalRewardIssued
    }
  }

  async backfillAllUsersForDate(date: Date): Promise<{
    processedUsers: number
    totalInstallments: number
    totalRewardNear: number
  }> {
    const dayStart = this.getStartOfDay(date)
    const stakings = await prisma.userStaking.findMany({
      where: {
        stakedAmount: { gt: 0 }
      },
      select: {
        userId: true
      }
    })

    let processedUsers = 0
    let totalInstallments = 0
    let totalRewardNear = 0

    for (const staking of stakings) {
      const result = await this.backfillUserRewards(staking.userId, {
        startDate: dayStart,
        endDate: dayStart
      })

      if (result.processedInstallments > 0) {
        processedUsers += 1
        totalInstallments += result.processedInstallments
        totalRewardNear += result.totalRewardNear
        console.log(
          `🔁 用户 ${staking.userId} 在 ${dayStart.toISOString().slice(0, 10)} 补发 ${result.processedInstallments} 次，` +
          `累计 ${result.totalRewardNear.toFixed(4)} NEAR`
        )
      }
    }

    return {
      processedUsers,
      totalInstallments,
      totalRewardNear
    }
  }

  private buildDailyDistributionKey(userId: number, timestamp: Date, slotIndex: number): string {
    return `daily:${userId}:${this.getStartOfDayIso(timestamp)}:${slotIndex}`
  }

  private buildBonusDistributionKey(
    userId: number,
    timestamp: Date,
    dayIndex: number,
    slotIndex: number
  ): string {
    return `bonus:${userId}:${this.getStartOfDayIso(timestamp)}:${dayIndex}:${slotIndex}`
  }

  private buildDistributionTxHash(distributionKey: string): string {
    const normalizedKey = distributionKey.replace(/[^a-zA-Z0-9]+/g, '_')
    return `auto_distribution_${normalizedKey}`
  }

}

export const rewardCalculationService = new RewardCalculationService()
